// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_THEMES_THEME_SERVICE_H_
#define CHROME_BROWSER_THEMES_THEME_SERVICE_H_

#include <memory>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "base/task/cancelable_task_tracker.h"
#include "chrome/browser/themes/theme_helper.h"
#include "chrome/common/buildflags.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/pref_change_registrar.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_id.h"
#include "ui/base/theme_provider.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_observer.h"

class BrowserThemePack;
class CustomThemeSupplier;
class Profile;
class ThemeServiceObserver;
class ThemeSyncableService;

namespace extensions {
class Extension;
}

namespace theme_service_internal {
class ThemeServiceTest;
}

class BrowserThemeProviderDelegate {
 public:
  virtual CustomThemeSupplier* GetThemeSupplier() const = 0;
};

class ThemeService : public KeyedService,
                     public ui::NativeThemeObserver,
                     public BrowserThemeProviderDelegate {
 public:
  // This class keeps track of the number of existing |ThemeReinstaller|
  // objects. When that number reaches 0 then unused themes will be deleted.
  class ThemeReinstaller {
   public:
    ThemeReinstaller(Profile* profile, base::OnceClosure installer);
    ThemeReinstaller(const ThemeReinstaller&) = delete;
    ThemeReinstaller& operator=(const ThemeReinstaller&) = delete;
    ~ThemeReinstaller();

    void Reinstall();

   private:
    base::OnceClosure installer_;
    const raw_ptr<ThemeService> theme_service_;
  };

  // Constant ID to use for all autogenerated themes.
  static const char kAutogeneratedThemeID[];

  // Creates a ThemeProvider with a custom theme supplier specified via
  // |delegate|. The return value must not outlive |profile|'s ThemeService.
  static std::unique_ptr<ui::ThemeProvider> CreateBoundThemeProvider(
      Profile* profile,
      BrowserThemeProviderDelegate* delegate);

  ThemeService(Profile* profile, const ThemeHelper& theme_helper);
  ThemeService(const ThemeService&) = delete;
  ThemeService& operator=(const ThemeService&) = delete;
  ~ThemeService() override;

  void Init();

  // KeyedService:
  void Shutdown() override;

  // Overridden from ui::NativeThemeObserver:
  void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;

  // Overridden from BrowserThemeProviderDelegate:
  CustomThemeSupplier* GetThemeSupplier() const override;

  // Set the current theme to the theme defined in |extension|.
  // |extension| must already be added to this profile's
  // ExtensionService.
  void SetTheme(const extensions::Extension* extension);

  // Similar to SetTheme, but doesn't show an undo infobar.
  void RevertToExtensionTheme(const std::string& extension_id);

  // Reset the theme to default.
  virtual void UseDefaultTheme();

  // Set the current theme to the system theme. On some platforms, the system
  // theme is the default theme.
  virtual void UseSystemTheme();

  // Returns true if the default theme and system theme are not the same on
  // this platform.
  virtual bool IsSystemThemeDistinctFromDefaultTheme() const;

  // Forwards to ThemeProviderBase::IsDefaultTheme().
  // Virtual for testing.
  virtual bool UsingDefaultTheme() const;

  // Whether we are using the system theme. On GTK, the system theme is the GTK
  // theme, not the "Classic" theme.
  virtual bool UsingSystemTheme() const;

  // Forwards to ThemeProviderBase::IsExtensionTheme().
  // Virtual for testing.
  virtual bool UsingExtensionTheme() const;

  // Forwards to ThemeProviderBase::IsAutogeneratedTheme().
  // Virtual for testing.
  virtual bool UsingAutogeneratedTheme() const;

  // Whether current theme colors are enforced through a policy.
  // Virtual for testing.
  virtual bool UsingPolicyTheme() const;

  // Gets the id of the last installed theme. (The theme may have been further
  // locally customized.)
  virtual std::string GetThemeID() const;

  // Uninstall theme extensions which are no longer in use.
  void RemoveUnusedThemes();

  // Returns the syncable service for syncing theme. The returned service is
  // owned by |this| object.
  virtual ThemeSyncableService* GetThemeSyncableService() const;

  // Gets the ThemeProvider for |profile|. This will be different for an
  // incognito profile and its original profile, even though both profiles use
  // the same ThemeService.
  static const ui::ThemeProvider& GetThemeProviderForProfile(Profile* profile);
  static CustomThemeSupplier* GetThemeSupplierForProfile(Profile* profile);

  // Builds an autogenerated theme from a given |color| and applies it.
  virtual void BuildAutogeneratedThemeFromColor(SkColor color);

  // Builds an autogenerated theme from a given |color| and applies it.
  virtual void BuildAutogeneratedThemeFromColor(SkColor color,
                                                bool store_in_prefs);

  // Returns the theme color for an autogenerated theme.
  virtual SkColor GetAutogeneratedThemeColor() const;

  // Builds an autogenerated theme from the applied policy color and applies it.
  virtual void BuildAutogeneratedPolicyTheme();

  // Returns the theme color for the current policy theme.
  virtual SkColor GetPolicyThemeColor() const;

  // Returns |ThemeService::ThemeReinstaller| for the current theme.
  std::unique_ptr<ThemeService::ThemeReinstaller>
  BuildReinstallerForCurrentTheme();

  // Virtual for testing.
  virtual void AddObserver(ThemeServiceObserver* observer);

  void RemoveObserver(ThemeServiceObserver* observer);

  const ThemeHelper& theme_helper_for_testing() const { return theme_helper_; }

  // Don't create "Cached Theme.pak" in the extension directory, for testing.
  static void DisableThemePackForTesting();

 protected:
  // Set a custom default theme instead of the normal default theme.
  virtual void SetCustomDefaultTheme(
      scoped_refptr<CustomThemeSupplier> theme_supplier);

  // Returns true if the ThemeService should use the system theme on startup.
  virtual bool ShouldInitWithSystemTheme() const;

  // Clears all the override fields and saves the dictionary.
  virtual void ClearAllThemeData();

  // Initialize current theme state data from preferences.
  virtual void InitFromPrefs();

  // Let all the browser views know that themes have changed.
  virtual void NotifyThemeChanged();

  // If there is an inconsistency in preferences, change preferences to a
  // consistent state.
  virtual void FixInconsistentPreferencesIfNeeded();

  Profile* profile() const { return profile_; }

  void set_ready() { ready_ = true; }

  // True if the theme service is ready to be used.
  // TODO(pkotwicz): Add DCHECKS to the theme service's getters once
  // ThemeSource no longer uses the ThemeService when it is not ready.
  bool ready_ = false;

 private:
  // This class implements ui::ThemeProvider on behalf of ThemeHelper and
  // keeps track of the incognito state and CustemThemeSupplier for the calling
  // code.
  class BrowserThemeProvider : public ui::ThemeProvider {
   public:
    BrowserThemeProvider(const ThemeHelper& theme_helper,
                         bool incognito,
                         const BrowserThemeProviderDelegate* delegate);
    BrowserThemeProvider(const BrowserThemeProvider&) = delete;
    BrowserThemeProvider& operator=(const BrowserThemeProvider&) = delete;
    ~BrowserThemeProvider() override;

    // Overridden from ui::ThemeProvider:
    gfx::ImageSkia* GetImageSkiaNamed(int id) const override;
    SkColor GetColor(int original_id) const override;
    color_utils::HSL GetTint(int original_id) const override;
    int GetDisplayProperty(int id) const override;
    bool ShouldUseNativeFrame() const override;
    bool HasCustomImage(int id) const override;
    bool HasCustomColor(int id) const override;
    base::RefCountedMemory* GetRawData(
        int id,
        ui::ResourceScaleFactor scale_factor) const override;

   private:
    absl::optional<SkColor> GetColorProviderColor(int id) const;
    CustomThemeSupplier* GetThemeSupplier() const;

    const ThemeHelper& theme_helper_;
    bool incognito_;
    raw_ptr<const BrowserThemeProviderDelegate> delegate_;
  };
  friend class BrowserThemeProvider;
  friend class theme_service_internal::ThemeServiceTest;

  // virtual for testing.
  virtual void DoSetTheme(const extensions::Extension* extension,
                          bool suppress_infobar);


  // Called when the extension service is ready.
  void OnExtensionServiceReady();

  // Migrate the theme to the new theme pack schema by recreating the data pack
  // from the extension.
  void MigrateTheme();

  // Replaces the current theme supplier with a new one and calls
  // StopUsingTheme() or StartUsingTheme() as appropriate.
  void SwapThemeSupplier(scoped_refptr<CustomThemeSupplier> theme_supplier);

  // Implementation of SetTheme() (and the fallback from InitFromPrefs() in
  // case we don't have a theme pack). |new_theme| indicates whether this is a
  // newly installed theme or a migration.
  void BuildFromExtension(const extensions::Extension* extension,
                          bool new_theme);

  // Callback when |pack| has finished or failed building.
  void OnThemeBuiltFromExtension(const extensions::ExtensionId& extension_id,
                                 scoped_refptr<BrowserThemePack> pack,
                                 bool new_theme);

#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
  // Returns true if the profile belongs to a supervised user.
  bool IsSupervisedUser() const;

  // Sets the current theme to the supervised user theme. Should only be used
  // for supervised user profiles.
  void SetSupervisedUserTheme();
#endif

  // Handles theme color policy pref updates. if policy value contains valid
  // color(s), sets browser theme accordingly.
  void HandlePolicyColorUpdate();

  // Functions that modify theme prefs.
  void ClearThemePrefs();
  void SetThemePrefsForExtension(const extensions::Extension* extension);
  void SetThemePrefsForColor(SkColor color);

  bool DisableExtension(const std::string& extension_id);

  raw_ptr<Profile> profile_;
  PrefChangeRegistrar pref_change_registrar_;

  const ThemeHelper& theme_helper_;
  scoped_refptr<CustomThemeSupplier> theme_supplier_;

  // The id of the theme extension which has just been installed but has not
  // been loaded yet. The theme extension with |installed_pending_load_id_| may
  // never be loaded if the install is due to updating a disabled theme.
  // |pending_install_id_| should be set to |kDefaultThemeID| if there are no
  // recently installed theme extensions
  std::string installed_pending_load_id_ = ThemeHelper::kDefaultThemeID;

  // The number of infobars currently displayed.
  int number_of_reinstallers_ = 0;

  // Declared before |theme_syncable_service_|, because ThemeSyncableService
  // removes itself from the |observers_| list on destruction.
  base::ObserverList<ThemeServiceObserver> observers_;

  std::unique_ptr<ThemeSyncableService> theme_syncable_service_;

#if BUILDFLAG(ENABLE_EXTENSIONS)
  class ThemeObserver;
  std::unique_ptr<ThemeObserver> theme_observer_;
#endif

  BrowserThemeProvider original_theme_provider_;
  BrowserThemeProvider incognito_theme_provider_;

  // Allows us to cancel building a theme pack from an extension.
  base::CancelableTaskTracker build_extension_task_tracker_;

  // The ID of the theme that's currently being built on a different thread.
  // We hold onto this just to be sure not to uninstall the extension view
  // RemoveUnusedThemes while it's still being built.
  std::string building_extension_id_;

  base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
      native_theme_observation_{this};

  base::WeakPtrFactory<ThemeService> weak_ptr_factory_{this};
};

#endif  // CHROME_BROWSER_THEMES_THEME_SERVICE_H_
